package search

import (
	"database/sql"
	"gitee.com/tziye/renai-go/common"
	"gitee.com/tziye/renai-go/renai"
	"gitee.com/tziye/renai-go/sqlproxy"
)

type SearchOperator struct {
	RenaiClient *renai.RenaiClient
	Db          *sql.DB
}

func NewSearchOperator(renaiClient *renai.RenaiClient) *SearchOperator {
	return &SearchOperator{
		RenaiClient: renaiClient,
		Db:          renaiClient.Db,
	}
}

func (searchOperator *SearchOperator) Save(text RenaiText) {
	searchOperator.commonSave(text.Module, []RenaiText{text})
}

func (searchOperator *SearchOperator) BatchSave(texts []RenaiText) {
	searchOperator.commonSave(texts[0].Module, texts)
}

func (searchOperator *SearchOperator) commonSave(module string, texts []RenaiText) {
	sqlproxy.CallRow(searchOperator.Db, common.TEXT_SAVE, module, common.String(texts))
}

func (searchOperator *SearchOperator) Delete(module string, businessId string) bool {
	return searchOperator.commonDelete(module, []string{businessId}) > 0
}

func (searchOperator *SearchOperator) BatchDelete(module string, businessIds []string) int {
	return searchOperator.commonDelete(module, businessIds)
}

func (searchOperator *SearchOperator) commonDelete(module string, businessIds []string) int {
	row := sqlproxy.CallRow(searchOperator.Db, common.TEXT_DELETE, module, common.String(businessIds))
	var updated sql.NullInt32
	err := row.Scan(&updated)
	common.PanicErr(err)
	return int(updated.Int32)
}

func (searchOperator *SearchOperator) Drop(module string) {
	sqlproxy.CallRow(searchOperator.Db, common.TEXT_DROP, module)
}

func (searchOperator *SearchOperator) Search(module string, searchType string, word string, withContent bool, start int, count int) []RenaiText {
	op := SearchOps{
		SearchType: searchType,
		Words:      []string{word},
	}
	script := transferToScript([]SearchOps{op})
	return searchOperator.commonSearch(module, script, withContent, start, count)
}

func (searchOperator *SearchOperator) SearchWords(module string, searchType string, words []string, withContent bool, start int, count int) []RenaiText {
	op := SearchOps{
		SearchType: searchType,
		Words:      words,
	}
	script := transferToScript([]SearchOps{op})
	return searchOperator.commonSearch(module, script, withContent, start, count)
}

func (searchOperator *SearchOperator) SearchOps(module string, ops []SearchOps, withContent bool, start int, count int) []RenaiText {
	script := transferToScript(ops)
	return searchOperator.commonSearch(module, script, withContent, start, count)
}

func (searchOperator *SearchOperator) SearchScript(module string, script string, withContent bool, start int, count int) []RenaiText {
	return searchOperator.commonSearch(module, script, withContent, start, count)
}

func (searchOperator *SearchOperator) commonSearch(module string, script string, withContent bool, start int, count int) []RenaiText {
	wc := 1
	if !withContent {
		wc = -1
	}
	rows := sqlproxy.CallRows(searchOperator.Db, common.TEXT_SEARCH, module, script, wc, start, count)
	defer rows.Close()
	var texts []RenaiText
	for rows.Next() {
		var text RenaiText
		if withContent {
			err := rows.Scan(&text.Id, &text.Module, &text.BusinessId, &text.Content, &text.Score)
			common.PanicErr(err)
		} else {
			err := rows.Scan(&text.BusinessId, &text.Score)
			common.PanicErr(err)
		}
		texts = append(texts, text)
	}
	return texts
}

type RenaiText struct {
	Id         int64
	Module     string
	BusinessId string
	Content    string
	Score      string
}

func NewRenaiText(module string, businessId string, value any) RenaiText {
	return RenaiText{
		Module:     module,
		BusinessId: businessId,
		Content:    common.String(value),
	}
}

func NewRenaiTextSimple(businessId string, value any) RenaiText {
	return RenaiText{
		BusinessId: businessId,
		Content:    common.String(value),
	}
}

const (
	Natural       = "Natural"
	BoolMatch     = "BoolMatch"
	BoolMust      = "BoolMust"
	BoolMustNot   = "BoolMustNot"
	BoolStartWith = "BoolStartWith"
	BoolLower     = "BoolLower"
	Expansion     = "Expansion"
)

type SearchOps struct {
	SearchType string
	Words      []string
}

func mode(searchType string) string {
	switch searchType {
	case Natural:
		return "IN NATURAL LANGUAGE MODE"
	case BoolMatch, BoolMust, BoolMustNot, BoolStartWith, BoolLower:
		return "IN BOOLEAN MODE"
	case Expansion:
		return "WITH QUERY EXPANSION"
	default:
		return ""
	}
}

func symbol(searchType string) string {
	switch searchType {
	case Natural, Expansion, BoolMatch:
		return ""
	case BoolMust:
		return "+"
	case BoolMustNot:
		return "-"
	case BoolStartWith:
		return "*"
	case BoolLower:
		return "~"
	default:
		return ""
	}
}

func transferToScript(ops []SearchOps) string {
	md := mode(ops[0].SearchType)
	str := "'"
	for _, op := range ops {
		sbl := symbol(op.SearchType)
		for _, word := range op.Words {
			if BoolStartWith == op.SearchType {
				str += "\"" + word + "\"" + sbl + " "
			} else {
				str += sbl + "\"" + word + "\"" + " "
			}
		}
	}
	str += "' " + md
	return str
}

// RenaiSearch

type RenaiSearch struct {
	SearchOperator *SearchOperator
	Module         string
}

func NewRenaiSearch(renaiClient *renai.RenaiClient, module string) *RenaiSearch {
	return &RenaiSearch{
		SearchOperator: NewSearchOperator(renaiClient),
		Module:         module,
	}
}

func (renaiSearch *RenaiSearch) SaveText(text RenaiText) {
	text.Module = renaiSearch.Module
	renaiSearch.SearchOperator.Save(text)
}

func (renaiSearch *RenaiSearch) BatchSaveText(texts []RenaiText) {
	for _, text := range texts {
		text.Module = renaiSearch.Module
	}
	renaiSearch.SearchOperator.BatchSave(texts)
}

func (renaiSearch *RenaiSearch) Save(businessId string, value any) {
	text := NewRenaiText(renaiSearch.Module, businessId, value)
	renaiSearch.SearchOperator.Save(text)
}

func (renaiSearch *RenaiSearch) BatchSave(values map[string]any) {
	texts := make([]RenaiText, 0)
	for k, v := range values {
		text := NewRenaiText(renaiSearch.Module, k, v)
		texts = append(texts, text)
	}
	renaiSearch.SearchOperator.BatchSave(texts)
}

func (renaiSearch *RenaiSearch) Delete(businessId string) bool {
	return renaiSearch.SearchOperator.Delete(renaiSearch.Module, businessId)
}

func (renaiSearch *RenaiSearch) BatchDelete(businessIds []string) int {
	return renaiSearch.SearchOperator.BatchDelete(renaiSearch.Module, businessIds)
}

func (renaiSearch *RenaiSearch) Drop() {
	renaiSearch.SearchOperator.Drop(renaiSearch.Module)
}

func (renaiSearch *RenaiSearch) MatchText(word string) []RenaiText {
	return renaiSearch.SearchOperator.Search(renaiSearch.Module, Natural, word, true, 0, 100)
}

func (renaiSearch *RenaiSearch) MatchTextWords(words []string) []RenaiText {
	return renaiSearch.SearchOperator.SearchWords(renaiSearch.Module, Natural, words, true, 0, 100)
}

func (renaiSearch *RenaiSearch) MatchTextPage(word string, start int, count int) []RenaiText {
	return renaiSearch.SearchOperator.Search(renaiSearch.Module, Natural, word, true, start, count)
}

func (renaiSearch *RenaiSearch) MatchTextWordsPage(words []string, start int, count int) []RenaiText {
	return renaiSearch.SearchOperator.SearchWords(renaiSearch.Module, Natural, words, true, start, count)
}

func (renaiSearch *RenaiSearch) Match(word string) []string {
	texts := renaiSearch.SearchOperator.Search(renaiSearch.Module, Natural, word, true, 0, 100)
	return fetchContent(texts)
}

func (renaiSearch *RenaiSearch) MatchWords(words []string) []string {
	texts := renaiSearch.SearchOperator.SearchWords(renaiSearch.Module, Natural, words, true, 0, 100)
	return fetchContent(texts)
}

func (renaiSearch *RenaiSearch) MatchPage(word string, start int, count int) []string {
	texts := renaiSearch.SearchOperator.Search(renaiSearch.Module, Natural, word, true, start, count)
	return fetchContent(texts)
}

func (renaiSearch *RenaiSearch) MatchWordsPage(words []string, start int, count int) []string {
	texts := renaiSearch.SearchOperator.SearchWords(renaiSearch.Module, Natural, words, true, start, count)
	return fetchContent(texts)
}

func fetchContent(texts []RenaiText) []string {
	contents := make([]string, 0)
	for _, text := range texts {
		contents = append(contents, text.Content)
	}
	return contents
}

func (renaiSearch *RenaiSearch) MatchId(word string) []string {
	texts := renaiSearch.SearchOperator.Search(renaiSearch.Module, Natural, word, false, 0, 100)
	return fetchBusinessId(texts)
}

func (renaiSearch *RenaiSearch) MatchIdWords(words []string) []string {
	texts := renaiSearch.SearchOperator.SearchWords(renaiSearch.Module, Natural, words, false, 0, 100)
	return fetchBusinessId(texts)
}

func (renaiSearch *RenaiSearch) MatchIdPage(word string, start int, count int) []string {
	texts := renaiSearch.SearchOperator.Search(renaiSearch.Module, Natural, word, false, start, count)
	return fetchBusinessId(texts)
}

func (renaiSearch *RenaiSearch) MatchIdWordsPage(words []string, start int, count int) []string {
	texts := renaiSearch.SearchOperator.SearchWords(renaiSearch.Module, Natural, words, false, start, count)
	return fetchBusinessId(texts)
}

func fetchBusinessId(texts []RenaiText) []string {
	businessIds := make([]string, 0)
	for _, text := range texts {
		businessIds = append(businessIds, text.BusinessId)
	}
	return businessIds
}

type RenaiBoolSearch struct {
	RenaiSearch *RenaiSearch
	Ops         []SearchOps
}

func (renaiSearch *RenaiSearch) Bool() *RenaiBoolSearch {
	return &RenaiBoolSearch{
		RenaiSearch: renaiSearch,
		Ops:         make([]SearchOps, 0),
	}
}

func (boolSearch *RenaiBoolSearch) Match(word string) *RenaiBoolSearch {
	op := SearchOps{
		SearchType: BoolMatch,
		Words:      []string{word},
	}
	boolSearch.Ops = append(boolSearch.Ops, op)
	return boolSearch
}

func (boolSearch *RenaiBoolSearch) MatchWords(words []string) *RenaiBoolSearch {
	op := SearchOps{
		SearchType: BoolMatch,
		Words:      words,
	}
	boolSearch.Ops = append(boolSearch.Ops, op)
	return boolSearch
}

func (boolSearch *RenaiBoolSearch) Must(word string) *RenaiBoolSearch {
	op := SearchOps{
		SearchType: BoolMust,
		Words:      []string{word},
	}
	boolSearch.Ops = append(boolSearch.Ops, op)
	return boolSearch
}

func (boolSearch *RenaiBoolSearch) MustWords(words []string) *RenaiBoolSearch {
	op := SearchOps{
		SearchType: BoolMust,
		Words:      words,
	}
	boolSearch.Ops = append(boolSearch.Ops, op)
	return boolSearch
}

func (boolSearch *RenaiBoolSearch) MustNot(word string) *RenaiBoolSearch {
	op := SearchOps{
		SearchType: BoolMustNot,
		Words:      []string{word},
	}
	boolSearch.Ops = append(boolSearch.Ops, op)
	return boolSearch
}

func (boolSearch *RenaiBoolSearch) MustNotWords(words []string) *RenaiBoolSearch {
	op := SearchOps{
		SearchType: BoolMust,
		Words:      words,
	}
	boolSearch.Ops = append(boolSearch.Ops, op)
	return boolSearch
}

func (boolSearch *RenaiBoolSearch) StartWith(word string) *RenaiBoolSearch {
	op := SearchOps{
		SearchType: BoolStartWith,
		Words:      []string{word},
	}
	boolSearch.Ops = append(boolSearch.Ops, op)
	return boolSearch
}

func (boolSearch *RenaiBoolSearch) StartWithWords(words []string) *RenaiBoolSearch {
	op := SearchOps{
		SearchType: BoolStartWith,
		Words:      words,
	}
	boolSearch.Ops = append(boolSearch.Ops, op)
	return boolSearch
}

func (boolSearch *RenaiBoolSearch) Lower(word string) *RenaiBoolSearch {
	op := SearchOps{
		SearchType: BoolLower,
		Words:      []string{word},
	}
	boolSearch.Ops = append(boolSearch.Ops, op)
	return boolSearch
}

func (boolSearch *RenaiBoolSearch) LowerWords(words []string) *RenaiBoolSearch {
	op := SearchOps{
		SearchType: BoolLower,
		Words:      words,
	}
	boolSearch.Ops = append(boolSearch.Ops, op)
	return boolSearch
}

func (boolSearch *RenaiBoolSearch) ExecText() []RenaiText {
	return boolSearch.RenaiSearch.SearchOperator.SearchOps(boolSearch.RenaiSearch.Module, boolSearch.Ops,
		true, 0, 100)
}

func (boolSearch *RenaiBoolSearch) ExecTextPage(start int, count int) []RenaiText {
	return boolSearch.RenaiSearch.SearchOperator.SearchOps(boolSearch.RenaiSearch.Module, boolSearch.Ops,
		true, start, count)
}

func (boolSearch *RenaiBoolSearch) Exec() []string {
	texts := boolSearch.RenaiSearch.SearchOperator.SearchOps(boolSearch.RenaiSearch.Module, boolSearch.Ops,
		true, 0, 100)
	return fetchContent(texts)
}

func (boolSearch *RenaiBoolSearch) ExecPage(start int, count int) []string {
	texts := boolSearch.RenaiSearch.SearchOperator.SearchOps(boolSearch.RenaiSearch.Module, boolSearch.Ops,
		true, start, count)
	return fetchContent(texts)
}

func (boolSearch *RenaiBoolSearch) ExecId() []string {
	texts := boolSearch.RenaiSearch.SearchOperator.SearchOps(boolSearch.RenaiSearch.Module, boolSearch.Ops,
		false, 0, 100)
	return fetchBusinessId(texts)
}

func (boolSearch *RenaiBoolSearch) ExecIdPage(start int, count int) []string {
	texts := boolSearch.RenaiSearch.SearchOperator.SearchOps(boolSearch.RenaiSearch.Module, boolSearch.Ops,
		false, start, count)
	return fetchBusinessId(texts)
}

func (renaiSearch *RenaiSearch) ExpansionText(word string) []RenaiText {
	return renaiSearch.SearchOperator.Search(renaiSearch.Module, Expansion, word, true, 0, 100)
}

func (renaiSearch *RenaiSearch) ExpansionTextPage(word string, start int, count int) []RenaiText {
	return renaiSearch.SearchOperator.Search(renaiSearch.Module, Expansion, word, true, start, count)
}

func (renaiSearch *RenaiSearch) Expansion(word string) []string {
	texts := renaiSearch.SearchOperator.Search(renaiSearch.Module, Expansion, word, true, 0, 100)
	return fetchContent(texts)
}

func (renaiSearch *RenaiSearch) ExpansionPage(word string, start int, count int) []string {
	texts := renaiSearch.SearchOperator.Search(renaiSearch.Module, Expansion, word, true, start, count)
	return fetchContent(texts)
}

func (renaiSearch *RenaiSearch) ExpansionId(word string) []string {
	texts := renaiSearch.SearchOperator.Search(renaiSearch.Module, Expansion, word, false, 0, 100)
	return fetchBusinessId(texts)
}

func (renaiSearch *RenaiSearch) ExpansionIdPage(word string, start int, count int) []string {
	texts := renaiSearch.SearchOperator.Search(renaiSearch.Module, Expansion, word, false, start, count)
	return fetchBusinessId(texts)
}

func (renaiSearch *RenaiSearch) ScriptText(script string) []RenaiText {
	return renaiSearch.SearchOperator.SearchScript(renaiSearch.Module, script, true, 0, 100)
}

func (renaiSearch *RenaiSearch) ScriptTextPage(script string, start int, count int) []RenaiText {
	return renaiSearch.SearchOperator.SearchScript(renaiSearch.Module, script, true, start, count)
}

func (renaiSearch *RenaiSearch) Script(script string) []string {
	texts := renaiSearch.SearchOperator.SearchScript(renaiSearch.Module, script, true, 0, 100)
	return fetchContent(texts)
}

func (renaiSearch *RenaiSearch) ScriptPage(script string, start int, count int) []string {
	texts := renaiSearch.SearchOperator.SearchScript(renaiSearch.Module, script, true, start, count)
	return fetchContent(texts)
}

func (renaiSearch *RenaiSearch) ScriptId(script string) []string {
	texts := renaiSearch.SearchOperator.SearchScript(renaiSearch.Module, script, false, 0, 100)
	return fetchBusinessId(texts)
}

func (renaiSearch *RenaiSearch) ScriptIdPage(script string, start int, count int) []string {
	texts := renaiSearch.SearchOperator.SearchScript(renaiSearch.Module, script, false, start, count)
	return fetchBusinessId(texts)
}
